using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;
using UnityEditor.Profiling;
using UnityEditorInternal;
using UnityEngine;

namespace SGEngine.ProfilerEx
{
    public class ProfilerExData : Notifier
    {
        public const string k_EventDataChange = "DataChange";
        public const string k_EventDataChangeNotRefreshView = "DataChangeNotRefreshView";
        private static ProfilerExData m_Instance;

        public static ProfilerExData Instance
        {
            get
            {
                if (m_Instance == null)
                {
                    m_Instance = new ProfilerExData();
                }
                return m_Instance;
            }
        }

        private ProfilerExData()
        {
        }

        private ProfilerTreeData m_ProfilerTreeData = new ProfilerTreeData(ProfilerTreeData.GetRootTreeNode());

        public ProfilerTreeData ProfilerTreeData
        {
            get { return m_ProfilerTreeData; }
            set
            {
                if (m_ProfilerTreeData != null)
                {
                    m_ProfilerTreeData.Dispose();
                }
                m_ProfilerTreeData = value;
            }
        }

        /// <summary>
        /// 保存二进制文件到本地
        /// </summary>
        /// <param name="filePath"></param>
        public void SaveToLocal(string filePath)
        {
            if (!FileUtility.IsValidFilePath(filePath))
            {
                return;
            }
            using (FileStream fileStream = File.Create(filePath))
            {
                BinaryFormatter formatter = new BinaryFormatter();
                formatter.Serialize(fileStream, ProfilerTreeData.Root);
            }
        }

        public void LoadFromLocal(string filePath)
        {
            if (!File.Exists(filePath))
            {
                return;
            }

            using (FileStream fileStream = File.Open(filePath, FileMode.Open))
            {
                BinaryFormatter formatter = new BinaryFormatter();
                var root = (ProfilerTreeNode)formatter.Deserialize(fileStream);
                ProfilerTreeData = new ProfilerTreeData(root);
            }
            RaiseEvent(k_EventDataChange);
        }
        
        public void LoadDataByProfilerData()
        {
            ProfilerTreeData = new ProfilerTreeData(ProfilerTreeData.GetRootTreeNode());
            // 遍历所有帧,加载数据
            for (int i = ProfilerDriver.firstFrameIndex; i <= ProfilerDriver.lastFrameIndex ; i++)
            {
                MergeNewProfilerFrameRecordedInternal(i);
            }
            RaiseEvent(k_EventDataChange);
        }

        public void MergeNewProfilerFrameRecorded(int newFrameIndex)
        {
            MergeNewProfilerFrameRecordedInternal(newFrameIndex);
            RaiseEvent(k_EventDataChangeNotRefreshView);
        }
        
        public void Clear()
        {
            ProfilerTreeData = new ProfilerTreeData(ProfilerTreeData.GetRootTreeNode());
            RaiseEvent(k_EventDataChange);
        }

        private void MergeNewProfilerFrameRecordedInternal(int newFrameIndex)
        {
            var threadDescs = ProfilerExUtility.GetAllThreadListByFrameIdx(newFrameIndex);
            foreach (var threadDesc in threadDescs)
            {
                if (!IsThreadNeedRecord(threadDesc))
                {
                    continue;
                }
                using (var frameDataView = ProfilerExUtility.GetFrameDataView(newFrameIndex, threadDesc.ThreadIndex))   // 这里需要释放掉 C++数据
                {
                    using ( var frameTree = SetUpProfilerTreeDate(frameDataView))
                    {
                        // 强制设置下线程名字
                        var firstNode= frameTree.Root.Children[0];
                        firstNode.Name = $"{threadDesc.GroupName}.{threadDesc.ThreadName}";
                        if (firstNode.Children.Count > 0)
                        {
                            firstNode.CallCount = firstNode.Children.Max(node => node.CallCount);
                        }
                        ProfilerTreeData.MergeTrees(ProfilerTreeData, frameTree);
                    }
                }
            }
        }

        private bool IsThreadNeedRecord(ProfilerExUtility.FrameThreadDesc frameThreadDesc)
        {
            var compareString = string.IsNullOrEmpty(frameThreadDesc.GroupName)
                ? frameThreadDesc.ThreadName
                : frameThreadDesc.GroupName;

            foreach (var option in ProfilerGCViewController.s_SelectOptions)
            {
                if (option.IsSelect && option.Msg.Equals(compareString))
                {
                    return true;
                }
            }
            return false;
        }

        private ProfilerTreeData SetUpProfilerTreeDate(HierarchyFrameDataView frameDataView)
        {
            var root = ProfilerTreeData.GetRootTreeNode();
            SetUpProfilerTreeNode(frameDataView, frameDataView.GetRootItemID(), ref root);
            return new ProfilerTreeData(root);
        }
        
        private void SetUpProfilerTreeNode(HierarchyFrameDataView frameDataView, int id, ref ProfilerTreeNode root)
        {
            // 数据转换
            var name = frameDataView.GetItemName(id);
            var callNum = (int)frameDataView.GetItemColumnDataAsDouble(id, ProfilerExUtility.k_CallNumColumnIdx);
            var gc = (long)frameDataView.GetItemColumnDataAsDouble(id, ProfilerExUtility.k_GCColumnIdx);
            var timeMs = frameDataView.GetItemColumnDataAsDouble(id, ProfilerExUtility.k_TimeMsColumnIdx);
            var selfMs = frameDataView.GetItemColumnDataAsDouble(id, ProfilerExUtility.k_SelfMsColumnIdx);
            var depth = frameDataView.GetItemDepth(id);
            var node = ProfilerTreeNode.RequestNode(name, depth, gc, 0, timeMs, selfMs, callNum);
            root.AddChild(node);
            if (frameDataView.HasItemChildren(id))
            {
                var childrenIds = new List<int>();
                frameDataView.GetItemChildren(id, childrenIds);
                foreach (var childId in childrenIds)
                {
                    SetUpProfilerTreeNode(frameDataView, childId, ref node);
                } 
            }
        }
    }
}